diff --git a/assets/src/bundles/browse/swhid-utils.js b/assets/src/bundles/browse/swhid-utils.js
--- a/assets/src/bundles/browse/swhid-utils.js
+++ b/assets/src/bundles/browse/swhid-utils.js
@@ -112,7 +112,7 @@
   $('#swh-identifiers').tabSlideOut(tabSlideOptions);
 
   // set the tab visible once the close animation is terminated
-  $('#swh-identifiers').css('display', 'block');
+  $('#swh-identifiers').addClass('d-none d-sm-block');
   $('.swhid-context-option').trigger('click');
 
   // highlighted code lines changed
diff --git a/assets/src/bundles/webapp/code-highlighting.js b/assets/src/bundles/webapp/code-highlighting.js
--- a/assets/src/bundles/webapp/code-highlighting.js
+++ b/assets/src/bundles/webapp/code-highlighting.js
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2018-2019  The Software Heritage developers
+ * Copyright (C) 2018-2021  The Software Heritage developers
  * See the AUTHORS file at the top-level directory of this distribution
  * License: GNU Affero General Public License version 3, or any later version
  * See top-level LICENSE file for more information
@@ -22,21 +22,35 @@
   return lineTd;
 }
 
+// function to highlight a range of lines
+export function highlightLines(first, last) {
+  if (!first) {
+    return;
+  }
+  if (!last) {
+    last = first;
+  }
+  for (let i = first; i <= last; ++i) {
+    highlightLine(i);
+  }
+}
+
 // function to reset highlighting
 export function resetHighlightedLines() {
   firstHighlightedLine = null;
   $('.hljs-ln-line[data-line-number]').css('background-color', 'inherit');
 }
 
-export function scrollToLine(lineDomElt) {
+export function scrollToLine(lineDomElt, offset = 70) {
   if ($(lineDomElt).closest('.swh-content').length > 0) {
     $('html, body').animate({
-      scrollTop: $(lineDomElt).offset().top - 70
+      scrollTop: $(lineDomElt).offset().top - offset
     }, 500);
   }
 }
 
-export async function highlightCode(showLineNumbers = true, selector = 'code') {
+export async function highlightCode(showLineNumbers = true, selector = 'code',
+                                    enableLinesSelection = true) {
 
   await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs');
 
@@ -60,9 +74,7 @@
     } else if (lines[0] < lines[lines.length - 1]) {
       firstHighlightedLine = parseInt(lines[0]);
       scrollToLine(highlightLine(lines[0]));
-      for (let i = lines[0] + 1; i <= lines[lines.length - 1]; ++i) {
-        highlightLine(i);
-      }
+      highlightLines(lines[0] + 1, lines[lines.length - 1]);
     }
   }
 
@@ -75,7 +87,7 @@
       }
     });
 
-    if (!showLineNumbers) {
+    if (!showLineNumbers || !enableLinesSelection) {
       return;
     }
 
@@ -88,9 +100,7 @@
         if (evt.shiftKey && firstHighlightedLine && line > firstHighlightedLine) {
           const firstLine = firstHighlightedLine;
           resetHighlightedLines();
-          for (let i = firstLine; i <= line; ++i) {
-            highlightLine(i);
-          }
+          highlightLines(firstLine, line);
           firstHighlightedLine = firstLine;
           window.location.hash = `#L${firstLine}-L${line}`;
         } else {
diff --git a/assets/src/bundles/webapp/iframes.js b/assets/src/bundles/webapp/iframes.js
new file mode 100644
--- /dev/null
+++ b/assets/src/bundles/webapp/iframes.js
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2021  The Software Heritage developers
+ * See the AUTHORS file at the top-level directory of this distribution
+ * License: GNU Affero General Public License version 3, or any later version
+ * See top-level LICENSE file for more information
+ */
+
+export function showIframeInfoModal(objectType, objectSWHID) {
+  const html = `
+    <p>
+      You can embed that ${objectType} view in an external website
+      through the use of an iframe. Use the following HTML code
+      to do so.
+    </p>
+    <pre><code class="swh-iframe-html html">&lt;iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+        src="${window.location.origin}${Urls.swhid_iframe(objectSWHID.replaceAll('\n', ''))}"&gt;
+&lt;/iframe&gt;</code></pre>
+    <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+            src="${window.location.origin}${Urls.swhid_iframe(objectSWHID.replaceAll('\n', ''))}">
+    </iframe>`;
+  swh.webapp.showModalHtml(`Software Heritage ${objectType} iframe`, html, '1000px');
+  swh.webapp.highlightCode(false, '.swh-iframe-html');
+}
diff --git a/assets/src/bundles/webapp/index.js b/assets/src/bundles/webapp/index.js
--- a/assets/src/bundles/webapp/index.js
+++ b/assets/src/bundles/webapp/index.js
@@ -26,3 +26,4 @@
 export * from './sentry';
 export * from './math-typesetting';
 export * from './status-widget';
+export * from './iframes';
diff --git a/assets/src/bundles/webapp/webapp-utils.js b/assets/src/bundles/webapp/webapp-utils.js
--- a/assets/src/bundles/webapp/webapp-utils.js
+++ b/assets/src/bundles/webapp/webapp-utils.js
@@ -251,9 +251,11 @@
   $('#swh-web-modal-confirm').modal('show');
 }
 
-export function showModalHtml(title, html) {
+export function showModalHtml(title, html, width = '500px') {
   $('#swh-web-modal-html .modal-title').text(title);
   $('#swh-web-modal-html .modal-body').html(html);
+  $('#swh-web-modal-html .modal-dialog').css('max-width', width);
+  $('#swh-web-modal-html .modal-dialog').css('width', width);
   $('#swh-web-modal-html').modal('show');
 }
 
diff --git a/assets/src/bundles/webapp/webapp.css b/assets/src/bundles/webapp/webapp.css
--- a/assets/src/bundles/webapp/webapp.css
+++ b/assets/src/bundles/webapp/webapp.css
@@ -662,6 +662,7 @@
 }
 
 .swh-badge-html,
+.swh-iframe-html,
 .swh-badge-md,
 .swh-badge-rst {
     white-space: pre-wrap !important;
diff --git a/docs/index.rst b/docs/index.rst
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -15,6 +15,7 @@
    uri-scheme-api
    uri-scheme-browse
    uri-scheme-identifiers
+   uri-scheme-misc
 
 
 
diff --git a/docs/uri-scheme-misc.rst b/docs/uri-scheme-misc.rst
new file mode 100644
--- /dev/null
+++ b/docs/uri-scheme-misc.rst
@@ -0,0 +1,41 @@
+Miscellaneous URLs
+^^^^^^^^^^^^^^^^^^
+
+Iframe view for contents and directories
+----------------------------------------
+
+A subset of Software Heritage objects (contents and directories) can be embedded
+in external websites through the use of iframes. A dedicated endpoint serving
+a minimalist Web UI is available for that use case.
+
+.. http:get:: /embed/(swhid)/
+
+    Endpoint to embed Software Heritage objects in external websites using
+    an iframe.
+
+    :param string swhid: a SoftWare Heritage persistent IDentifier
+        object, or SWHID (see :ref:`persistent-identifiers` to learn more about its syntax)
+
+    :statuscode 200: no error
+    :statuscode 400: the provided identifier is malformed or
+      the object type is not supported by the view
+    :statuscode 404: requested object cannot be found in the archive
+
+    **Example:**
+
+    By adding HTML code similar to the one below in a web page,
+
+    .. code-block:: html
+
+      <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+              src="https://archive.softwareheritage.org/embed/swh:1:cnt:edc043a59197bcebc1d44fb70bf1b84cde3db791;origin=https://github.com/rdicosmo/parmap;visit=swh:1:snp:2d869aa00591d2ac8ec8e7abacdda563d413189d;anchor=swh:1:rev:f140dbc8b05aa3d341c70436a1920a06df9a0ed4;path=/src/parmap.ml">
+      </iframe>
+
+    you will obtain the following rendering.
+
+    .. raw:: html
+
+      <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+              src="https://archive.softwareheritage.org/embed/swh:1:cnt:edc043a59197bcebc1d44fb70bf1b84cde3db791;origin=https://github.com/rdicosmo/parmap;visit=swh:1:snp:2d869aa00591d2ac8ec8e7abacdda563d413189d;anchor=swh:1:rev:f140dbc8b05aa3d341c70436a1920a06df9a0ed4;path=/src/parmap.ml">
+      </iframe>
+
diff --git a/swh/web/common/utils.py b/swh/web/common/utils.py
--- a/swh/web/common/utils.py
+++ b/swh/web/common/utils.py
@@ -285,6 +285,7 @@
         ),
         "swh_web_version": get_distribution("swh.web").version,
         "visit_types": ORIGIN_VISIT_TYPES,
+        "iframe_mode": False,
     }
 
 
diff --git a/swh/web/misc/iframe.py b/swh/web/misc/iframe.py
new file mode 100644
--- /dev/null
+++ b/swh/web/misc/iframe.py
@@ -0,0 +1,310 @@
+# Copyright (C) 2021  The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from typing import Any, Dict, List, Optional
+
+from django.conf.urls import url
+from django.shortcuts import render
+from django.views.decorators.clickjacking import xframe_options_exempt
+
+from swh.model.hashutil import hash_to_bytes
+from swh.model.identifiers import (
+    CONTENT,
+    DIRECTORY,
+    REVISION,
+    SNAPSHOT,
+    ObjectType,
+    QualifiedSWHID,
+)
+from swh.web.browse.snapshot_context import get_snapshot_context
+from swh.web.browse.utils import (
+    content_display_max_size,
+    get_directory_entries,
+    prepare_content_for_display,
+    request_content,
+)
+from swh.web.common import archive
+from swh.web.common.exc import BadInputExc, NotFoundExc, http_status_code_message
+from swh.web.common.identifiers import get_swhid, get_swhids_info
+from swh.web.common.typing import SnapshotContext, SWHObjectInfo
+from swh.web.common.utils import gen_path_info, reverse
+
+
+def _get_content_rendering_data(cnt_swhid: QualifiedSWHID, path: str) -> Dict[str, Any]:
+    content_data = request_content(f"sha1_git:{cnt_swhid.object_id.hex()}")
+    content = None
+    language = None
+    mimetype = None
+    if content_data.get("raw_data") is not None:
+        content_display_data = prepare_content_for_display(
+            content_data["raw_data"], content_data["mimetype"], path
+        )
+        content = content_display_data["content_data"]
+        language = content_display_data["language"]
+        mimetype = content_display_data["mimetype"]
+
+    return {
+        "content": content,
+        "content_size": content_data.get("length"),
+        "max_content_size": content_display_max_size,
+        "filename": path.split("/")[-1],
+        "encoding": content_data.get("encoding"),
+        "mimetype": mimetype,
+        "language": language,
+    }
+
+
+def _get_directory_rendering_data(
+    dir_swhid: QualifiedSWHID, focus_swhid: QualifiedSWHID, path: str,
+) -> Dict[str, Any]:
+    dirs, files = get_directory_entries(dir_swhid.object_id.hex())
+    for d in dirs:
+        if d["type"] == "rev":
+            d["url"] = None
+        else:
+            dir_swhid = QualifiedSWHID(
+                object_type=ObjectType.DIRECTORY,
+                object_id=hash_to_bytes(d["target"]),
+                origin=dir_swhid.origin,
+                visit=dir_swhid.visit,
+                anchor=dir_swhid.anchor,
+                path=(path or "/") + d["name"] + "/",
+            )
+            d["url"] = reverse(
+                "swhid-iframe",
+                url_args={"swhid": str(dir_swhid)},
+                query_params={"focus_swhid": str(focus_swhid)},
+            )
+
+    for f in files:
+        object_id = hash_to_bytes(f["target"])
+        cnt_swhid = QualifiedSWHID(
+            object_type=ObjectType.CONTENT,
+            object_id=object_id,
+            origin=dir_swhid.origin,
+            visit=dir_swhid.visit,
+            anchor=dir_swhid.anchor,
+            path=(path or "/") + f["name"],
+            lines=(focus_swhid.lines if object_id == focus_swhid.object_id else None),
+        )
+        f["url"] = reverse(
+            "swhid-iframe",
+            url_args={"swhid": str(cnt_swhid)},
+            query_params={"focus_swhid": str(focus_swhid)},
+        )
+
+    return {"dirs": dirs, "files": files}
+
+
+def _get_breacrumbs_data(
+    swhid: QualifiedSWHID,
+    focus_swhid: QualifiedSWHID,
+    path: str,
+    snapshot_context: Optional[SnapshotContext] = None,
+) -> List[Dict[str, Any]]:
+    breadcrumbs = []
+    filename = None
+    # strip any leading or trailing slash from path qualifier of SWHID
+    if path and path[0] == "/":
+        path = path[1:]
+    if path and path[-1] == "/":
+        path = path[:-1]
+    if swhid.object_type == ObjectType.CONTENT:
+        split_path = path.split("/")
+        filename = split_path[-1]
+        path = path[: -len(filename)]
+
+    path_info = gen_path_info(path) if path != "/" else []
+
+    root_dir = None
+    if snapshot_context and snapshot_context["root_directory"]:
+        root_dir = snapshot_context["root_directory"]
+    elif focus_swhid.object_type == ObjectType.DIRECTORY:
+        root_dir = focus_swhid.object_id.hex()
+
+    if root_dir:
+        root_dir_swhid = QualifiedSWHID(
+            object_type=ObjectType.DIRECTORY,
+            object_id=hash_to_bytes(root_dir),
+            origin=swhid.origin,
+            visit=swhid.visit,
+            anchor=swhid.anchor,
+        )
+        breadcrumbs.append(
+            {
+                "name": root_dir[:7],
+                "object_id": root_dir_swhid.object_id.hex(),
+                "path": "/",
+                "url": reverse(
+                    "swhid-iframe",
+                    url_args={"swhid": str(root_dir_swhid)},
+                    query_params={"focus_swhid": focus_swhid},
+                ),
+            }
+        )
+
+        for pi in path_info:
+            dir_info = archive.lookup_directory_with_path(root_dir, pi["path"])
+            dir_swhid = QualifiedSWHID(
+                object_type=ObjectType.DIRECTORY,
+                object_id=hash_to_bytes(dir_info["target"]),
+                origin=swhid.origin,
+                visit=swhid.visit,
+                anchor=swhid.anchor,
+                path="/" + pi["path"] + "/",
+            )
+            breadcrumbs.append(
+                {
+                    "name": pi["name"],
+                    "object_id": dir_swhid.object_id.hex(),
+                    "path": dir_swhid.path.decode("utf-8") if dir_swhid.path else "",
+                    "url": reverse(
+                        "swhid-iframe",
+                        url_args={"swhid": str(dir_swhid)},
+                        query_params={"focus_swhid": focus_swhid},
+                    ),
+                }
+            )
+    if filename:
+        breadcrumbs.append(
+            {
+                "name": filename,
+                "object_id": swhid.object_id.hex(),
+                "path": path,
+                "url": "",
+            }
+        )
+
+    return breadcrumbs
+
+
+@xframe_options_exempt
+def swhid_iframe(request, swhid: str):
+    """Django view that can be embedded in an iframe to display objects archived
+    by Software Heritage (currently contents and directories) in a minimalist
+    Web UI.
+    """
+    focus_swhid = request.GET.get("focus_swhid", swhid)
+    parsed_swhid = None
+    view_data = {}
+    breadcrumbs = []
+    swh_objects = []
+    snapshot_context = None
+    swhids_info_extra_context = {}
+    try:
+        parsed_swhid = get_swhid(swhid)
+        parsed_focus_swhid = get_swhid(focus_swhid)
+        path = parsed_swhid.path.decode("utf-8") if parsed_swhid.path else ""
+
+        snapshot_context = None
+        revision_id = None
+        if (
+            parsed_swhid.anchor
+            and parsed_swhid.anchor.object_type == ObjectType.REVISION
+        ):
+            revision_id = parsed_swhid.anchor.object_id.hex()
+        if parsed_swhid.origin or parsed_swhid.visit:
+            snapshot_context = get_snapshot_context(
+                origin_url=parsed_swhid.origin,
+                snapshot_id=parsed_swhid.visit.object_id.hex()
+                if parsed_swhid.visit
+                else None,
+                revision_id=revision_id,
+            )
+
+        error_info: Dict[str, Any] = {"status_code": 200, "description": ""}
+
+        if parsed_swhid and parsed_swhid.object_type == ObjectType.CONTENT:
+            view_data = _get_content_rendering_data(parsed_swhid, path)
+            swh_objects.append(
+                SWHObjectInfo(
+                    object_type=CONTENT, object_id=parsed_swhid.object_id.hex()
+                )
+            )
+
+        elif parsed_swhid and parsed_swhid.object_type == ObjectType.DIRECTORY:
+            view_data = _get_directory_rendering_data(
+                parsed_swhid, parsed_focus_swhid, path
+            )
+            swh_objects.append(
+                SWHObjectInfo(
+                    object_type=DIRECTORY, object_id=parsed_swhid.object_id.hex()
+                )
+            )
+
+        elif parsed_swhid:
+            error_info = {
+                "status_code": 400,
+                "description": (
+                    f"Objects of type {parsed_swhid.object_type} are not supported"
+                ),
+            }
+
+        swhids_info_extra_context["path"] = path
+        if parsed_swhid and view_data:
+            breadcrumbs = _get_breacrumbs_data(
+                parsed_swhid, parsed_focus_swhid, path, snapshot_context
+            )
+
+            if parsed_swhid.object_type == ObjectType.CONTENT and len(breadcrumbs) > 1:
+                swh_objects.append(
+                    SWHObjectInfo(
+                        object_type=DIRECTORY, object_id=breadcrumbs[-2]["object_id"]
+                    )
+                )
+                swhids_info_extra_context["path"] = breadcrumbs[-2]["path"]
+                swhids_info_extra_context["filename"] = breadcrumbs[-1]["name"]
+
+            if snapshot_context:
+                swh_objects.append(
+                    SWHObjectInfo(
+                        object_type=REVISION,
+                        object_id=snapshot_context["revision_id"] or "",
+                    )
+                )
+                swh_objects.append(
+                    SWHObjectInfo(
+                        object_type=SNAPSHOT,
+                        object_id=snapshot_context["snapshot_id"] or "",
+                    )
+                )
+
+    except BadInputExc as e:
+        error_info = {"status_code": 400, "description": f"BadInputExc: {str(e)}"}
+    except NotFoundExc as e:
+        error_info = {"status_code": 404, "description": f"NotFoundExc: {str(e)}"}
+    except Exception as e:
+        error_info = {"status_code": 500, "description": str(e)}
+
+    return render(
+        request,
+        "misc/iframe.html",
+        {
+            **view_data,
+            "iframe_mode": True,
+            "object_type": parsed_swhid.object_type.value if parsed_swhid else None,
+            "lines": parsed_swhid.lines if parsed_swhid else None,
+            "breadcrumbs": breadcrumbs,
+            "swhid": swhid,
+            "focus_swhid": focus_swhid,
+            "error_code": error_info["status_code"],
+            "error_message": http_status_code_message.get(error_info["status_code"]),
+            "error_description": error_info["description"],
+            "snapshot_context": None,
+            "swhids_info": get_swhids_info(
+                swh_objects, snapshot_context, swhids_info_extra_context
+            ),
+        },
+        status=error_info["status_code"],
+    )
+
+
+urlpatterns = [
+    url(
+        r"^embed/(?P<swhid>swh:[0-9]+:[a-z]+:[0-9a-f]+.*)$",
+        swhid_iframe,
+        name="swhid-iframe",
+    ),
+]
diff --git a/swh/web/misc/urls.py b/swh/web/misc/urls.py
--- a/swh/web/misc/urls.py
+++ b/swh/web/misc/urls.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2019  The Software Heritage developers
+# Copyright (C) 2019-2021  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU Affero General Public License version 3, or any later version
 # See top-level LICENSE file for more information
@@ -51,6 +51,7 @@
     url(r"^stat_counters/", _stat_counters, name="stat-counters"),
     url(r"^", include("swh.web.misc.badges")),
     url(r"^metrics/prometheus/$", prometheus_metrics, name="metrics-prometheus"),
+    url(r"^", include("swh.web.misc.iframe")),
 ]
 
 
diff --git a/swh/web/templates/includes/breadcrumbs.html b/swh/web/templates/includes/breadcrumbs.html
--- a/swh/web/templates/includes/breadcrumbs.html
+++ b/swh/web/templates/includes/breadcrumbs.html
@@ -1,24 +1,23 @@
 {% comment %}
-Copyright (C) 2017-2018  The Software Heritage developers
+Copyright (C) 2017-2021  The Software Heritage developers
 See the AUTHORS file at the top-level directory of this distribution
 License: GNU Affero General Public License version 3, or any later version
 See top-level LICENSE file for more information
 {% endcomment %}
 
 {% if breadcrumbs %}
-    {% if breadcrumbs|length > 1 or breadcrumbs.0.url %}
-        <nav class="bread-crumbs swh-browse-bread-crumbs">
-            <ul>
-            {% for bc in breadcrumbs %}
-                {% if bc.url %}
-                <li class="swh-path"><a href="{{ bc.url | safe }}">{{ bc.name }}</a></li>
-                <li>/</li>
-                {% else %}
-                <li>{{ bc.name }}</li>
-                {% endif %}
-            {% endfor %}
-            </ul>
-        </nav>
-    {% endif %}
+  {% if iframe_mode or breadcrumbs|length > 1 or breadcrumbs.0.url %}
+    <nav class="bread-crumbs swh-browse-bread-crumbs">
+      <ul>
+        {% for bc in breadcrumbs %}
+          {% if bc.url %}
+            <li class="swh-path"><a href="{{ bc.url | safe }}">{{ bc.name }}</a></li>
+            <li>/</li>
+          {% else %}
+            <li>{{ bc.name }}</li>
+          {% endif %}
+        {% endfor %}
+      </ul>
+    </nav>
+  {% endif %}
 {% endif %}
-
diff --git a/swh/web/templates/includes/content-display.html b/swh/web/templates/includes/content-display.html
--- a/swh/web/templates/includes/content-display.html
+++ b/swh/web/templates/includes/content-display.html
@@ -1,5 +1,5 @@
 {% comment %}
-Copyright (C) 2017-2020  The Software Heritage developers
+Copyright (C) 2017-2021  The Software Heritage developers
 See the AUTHORS file at the top-level directory of this distribution
 License: GNU Affero General Public License version 3, or any later version
 See top-level LICENSE file for more information
@@ -12,12 +12,14 @@
 {% if snapshot_context and snapshot_context.is_empty %}
   {% include "includes/empty-snapshot.html" %}
 {% else %}
-  <div class="card">
-    {% if filename %}
-      <div class="swh-content-filename card-header bg-gray-light swh-heading-color">
-        {{ filename }}
-      </div>
-    {% endif %}
+  {% if not iframe_mode %}
+    <div class="card">
+      {% if filename %}
+        <div class="swh-content-filename card-header bg-gray-light swh-heading-color">
+          {{ filename }}
+        </div>
+      {% endif %}
+  {% endif %}
     <div class="swh-content">
       {% if content_size > max_content_size %}
         Content is too large to be displayed (size is greater than {{ max_content_size|filesizeformat }}).
@@ -47,7 +49,9 @@
         {% include "includes/http-error.html" %}
       {% endif %}
     </div>
-  </div>
+  {% if not iframe_mode %}
+    </div>
+  {% endif %}
 
   {% if content %}
     <script>
@@ -59,7 +63,7 @@
         let codeContainer = $('code');
         let content = codeContainer.text();
 
-        swh.webapp.highlightCode();
+        swh.webapp.highlightCode(true, 'code', !{{ iframe_mode|jsonify }});
 
         function updateLanguage(language) {
           codeContainer.text(content);
@@ -72,7 +76,7 @@
           const newUrl = window.location.pathname + '?' + urlParams.toString() + window.location.hash;
           window.history.replaceState('', document.title, newUrl);
 
-          swh.webapp.highlightCode();
+          swh.webapp.highlightCode(true, 'code', !{{ iframe_mode|jsonify }});
         }
 
       {% endif %}
diff --git a/swh/web/templates/includes/directory-display.html b/swh/web/templates/includes/directory-display.html
--- a/swh/web/templates/includes/directory-display.html
+++ b/swh/web/templates/includes/directory-display.html
@@ -1,11 +1,13 @@
 {% comment %}
-Copyright (C) 2017-2020  The Software Heritage developers
+Copyright (C) 2017-2021  The Software Heritage developers
 See the AUTHORS file at the top-level directory of this distribution
 License: GNU Affero General Public License version 3, or any later version
 See top-level LICENSE file for more information
 {% endcomment %}
 
-{% include "includes/revision-info.html" %}
+{% if not iframe_mode %}
+  {% include "includes/revision-info.html" %}
+{% endif %}
 
 {% if snapshot_context and snapshot_context.is_empty %}
   {% include "includes/empty-snapshot.html" %}
@@ -53,7 +55,9 @@
       </tbody>
     </table>
   </div>
-  <hr class="mt-0 mb-2">
+  {% if not iframe_mode %}
+    <hr class="mt-0 mb-2">
+  {% endif %}
 {% elif "revision_found" in swh_object_metadata and swh_object_metadata.revision_found is False %}
   <i>Revision {{ swh_object_metadata.revision }} could not be found in the archive.</i>
   <br/>
diff --git a/swh/web/templates/includes/http-error.html b/swh/web/templates/includes/http-error.html
--- a/swh/web/templates/includes/http-error.html
+++ b/swh/web/templates/includes/http-error.html
@@ -1,5 +1,5 @@
 {% comment %}
-Copyright (C) 2018  The Software Heritage developers
+Copyright (C) 2018-2021  The Software Heritage developers
 See the AUTHORS file at the top-level directory of this distribution
 License: GNU Affero General Public License version 3, or any later version
 See top-level LICENSE file for more information
@@ -18,16 +18,18 @@
 
   <div class="swh-http-error-desc">
     <pre>{{ error_description }}</pre>
-    <div>
-      <a class="btn" onclick="window.history.back();">
-        <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
-        Go back to previous page
-      </a>
-      or
-      <a class="btn" href="{% url 'swh-web-homepage' %}">
-        <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
-        Go back to homepage
-      </a>
-    </div>
+    {% if not iframe_mode %}
+      <div>
+        <a class="btn" onclick="window.history.back();">
+          <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
+          Go back to previous page
+        </a>
+        or
+        <a class="btn" href="{% url 'swh-web-homepage' %}">
+          <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
+          Go back to homepage
+        </a>
+      </div>
+    {% endif %}
   </div>
 </div>
\ No newline at end of file
diff --git a/swh/web/templates/includes/show-swhids.html b/swh/web/templates/includes/show-swhids.html
--- a/swh/web/templates/includes/show-swhids.html
+++ b/swh/web/templates/includes/show-swhids.html
@@ -18,7 +18,8 @@
     <div id="swh-identifiers-content">
       <p>
         To reference or cite the objects present in the Software Heritage archive, permalinks based on
-        <a href="https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html">
+        <a target="_blank" rel="noopener noreferrer"
+           href="https://docs.softwahreheritage.org/devel/swh-model/persistent-identifiers.html">
           SoftWare Heritage persistent IDentifiers (SWHIDs)
         </a>
         must be used instead of copying and pasting the url from the address bar of the browser (as there is no guarantee the current URI
@@ -55,22 +56,31 @@
           {% endif %}
             <div class="card">
               <div class="card-body swhid-ui">
-                <div class="swh-badges">
-                  {% if snapshot_context and snapshot_context.origin_info %}
-                    <img class="swh-badge swh-badge-origin"
-                        src="{% url 'swh-badge' 'origin' snapshot_context.origin_info.url %}"
-                        onclick="swh.webapp.showBadgeInfoModal('origin', '{{ snapshot_context.origin_info.url|urlencode:"/?:@&" }}')"
-                        title="Click to display badge integration info">
-                  {% endif %}
-                  {% if swhid_info.object_id %}
-                    <img class="swh-badge swh-badge-{{ swhid_info.object_type }}"
-                        src="{% url 'swh-badge' swhid_info.object_type swhid_info.object_id %}"
-                        onclick="swh.webapp.showBadgeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())"
-                        title="Click to display badge integration info">
-                  {% endif %}
-                </div>
+                {% if not iframe_mode %}
+                  <div class="swh-badges">
+                    {% if snapshot_context and snapshot_context.origin_info %}
+                      <img class="swh-badge swh-badge-origin"
+                          src="{% url 'swh-badge' 'origin' snapshot_context.origin_info.url %}"
+                          onclick="swh.webapp.showBadgeInfoModal('origin', '{{ snapshot_context.origin_info.url|urlencode:"/?:@&" }}')"
+                          title="Click to display badge integration info">
+                    {% endif %}
+                    {% if swhid_info.object_id %}
+                      <img class="swh-badge swh-badge-{{ swhid_info.object_type }}"
+                          src="{% url 'swh-badge' swhid_info.object_type swhid_info.object_id %}"
+                          onclick="swh.webapp.showBadgeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())"
+                          title="Click to display badge integration info">
+                    {% endif %}
+                    {% if swhid_info.object_type == "content" or swhid_info.object_type == "directory" %}
+                      <a class="float-right" style="cursor: pointer;"
+                         onclick="swh.webapp.showIframeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())">
+                        Iframe embedding
+                      </a>
+                    {% endif %}
+                  </div>
+
+                {% endif %}
                 {% if swhid_info.object_id %}
-                <pre><a class="swhid" id="{{ swhid_info.swhid }}" href="{{ swhid_info.swhid_url }}">{{ swhid_info.swhid }}</a></pre>
+                <pre><a class="swhid" target="_blank" rel="noopener noreferrer" id="{{ swhid_info.swhid }}" href="{{ swhid_info.swhid_url }}">{{ swhid_info.swhid }}</a></pre>
                 {% endif %}
                 {% if swhid_info.swhid_with_context is not None %}
                   <div class="float-left">
diff --git a/swh/web/templates/misc/iframe.html b/swh/web/templates/misc/iframe.html
new file mode 100644
--- /dev/null
+++ b/swh/web/templates/misc/iframe.html
@@ -0,0 +1,182 @@
+{% comment %}
+Copyright (C) 2021  The Software Heritage developers
+See the AUTHORS file at the top-level directory of this distribution
+License: GNU Affero General Public License version 3, or any later version
+See top-level LICENSE file for more information
+{% endcomment %}
+
+<!DOCTYPE html>
+
+{% load static %}
+{% load render_bundle from webpack_loader %}
+{% load swh_templatetags %}
+
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <title>Software Heritage archived object</title>
+    <script src="{% url 'js_reverse' %}" type="text/javascript"></script>
+    {% render_bundle 'vendors' %}
+    {% render_bundle 'webapp' %}
+    {% render_bundle 'browse' %}
+    <script>
+/*
+@licstart  The following is the entire license notice for the JavaScript code in this page.
+
+Copyright (C) 2021  The Software Heritage developers
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+@licend  The above is the entire license notice for the JavaScript code in this page.
+*/
+    </script>
+    <style>
+      .card {
+        border: none;
+        margin-bottom: 0 !important;
+      }
+
+      .card-header {
+        position: fixed;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 80px;
+        border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+      }
+
+      .breadcrumbs-container {
+        border-top: 1px solid rgba(0, 0, 0, 0.125);
+        border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+        background: white;
+        margin-left: -20px;
+        margin-right: -20px;
+        margin-bottom: 5px;
+      }
+
+      .breadcrumbs-container ul {
+        padding-left: 25px;
+      }
+
+      .card-block {
+        margin-top: 78px;
+        overflow: auto;
+      }
+
+      .swh-content pre {
+        margin-top: 5px;
+        margin-bottom: 0;
+        padding: 0;
+      }
+
+      .swh-directory-table {
+        margin-top: 3px;
+      }
+
+      .bread-crumbs {
+        font-size: large;
+      }
+
+      .hljs-ln-numbers {
+        cursor: default !important;
+      }
+
+      #swh-identifiers {
+        top: 60px !important;
+      }
+
+      #swh-identifiers .card-body {
+        padding-top: 0;
+        padding-bottom: 0;
+        padding-left: 8px;
+      }
+
+    </style>
+  </head>
+
+  <body style="padding-bottom: 0;">
+    <div class="wrapper" style="margin-left: 0;">
+      <div class="content">
+        <div class="container-fluid">
+          {% include "includes/show-swhids.html" %}
+          <div class="card">
+            <div class="card-header bg-gray-light">
+              <div class="d-flex align-items-center">
+                <a class="mr-auto" href="https://www.softwareheritage.org"
+                   target="_blank" rel="noopener noreferrer">
+                  <div class="brand-text sitename">
+                    <img src="{% static 'img/swh-logo.svg' %}" style="height: 40px;"/>
+                    <span class="first-word pl-1">Software</span>
+                    <span class="second-word">Heritage</span>
+                  </div>
+                </a>
+                <div class="d-none d-md-block">
+                  Navigating in
+                  <img src="{% url 'swh-badge-swhid' swhid %}">
+                </div>
+                <div class="ml-auto d-flex align-items-center">
+                  {% if swhid != focus_swhid %}
+                    <a class="d-flex align-items-center pr-2" href="{% url 'swhid-iframe' focus_swhid %}"
+                        title="Reset view to its original state">
+                      <div class="d-none d-lg-block">Reset view</div>
+                      <i class="mdi mdi-refresh" aria-hidden="true"></i>
+                    </a>
+                  {% endif %}
+                  <a class="d-none d-lg-block" href="{% url 'browse-swhid' swhid %}"
+                     target="_blank" rel="noopener noreferrer">
+                    View in the archive
+                    <i class="mdi mdi-open-in-new" aria-hidden="true"></i>
+                  </a>
+                  <a class="d-lg-none" href="{% url 'browse-swhid' swhid %}"
+                      target="_blank" rel="noopener noreferrer"
+                      title="Go to archive">
+                    <i class="mdi mdi-open-in-new" aria-hidden="true"></i>
+                  </a>
+                </div>
+              </div>
+              <div class="d-flex align-items-center breadcrumbs-container mt-1">
+                {% include "includes/breadcrumbs.html" %}
+              </div>
+            </div>
+            <div class="card-block">
+              {% if error_code != 200 %}
+                {% include "includes/http-error.html" %}
+              {% elif object_type == "cnt" %}
+                {% include "includes/content-display.html" %}
+              {% elif object_type == "dir" %}
+                {% include "includes/directory-display.html" %}
+              {% endif %}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <a href="{% url 'jslicenses' %}" rel="jslicense" style="display: none;">
+      JavaScript license information
+    </a>
+    {% if object_type == "cnt" %}
+      <script>
+        setTimeout(function() {
+          {% if lines %}
+            swh.webapp.scrollToLine(swh.webapp.highlightLine({{ lines.0|jsonify }}), 100);
+            swh.webapp.highlightLines({{ lines.0|jsonify }}, {{ lines.1|jsonify }});
+          {% endif %}
+        }, 500);
+      </script>
+    {% endif %}
+  </body>
+
+</html>
diff --git a/swh/web/tests/data.py b/swh/web/tests/data.py
--- a/swh/web/tests/data.py
+++ b/swh/web/tests/data.py
@@ -20,6 +20,7 @@
 from swh.indexer.storage.model import OriginIntrinsicMetadataRow
 from swh.loader.git.from_disk import GitLoaderFromArchive
 from swh.model.hashutil import DEFAULT_ALGORITHMS, hash_to_hex
+from swh.model.identifiers import CoreSWHID, ObjectType, QualifiedSWHID
 from swh.model.model import (
     Content,
     Directory,
@@ -277,18 +278,35 @@
     revisions = set()
     releases = set()
     snapshots = set()
+    swhids = []
 
     content_path = {}
 
     # Get all objects loaded into the test archive
     common_metadata = {ORIGIN_METADATA_KEY: ORIGIN_METADATA_VALUE}
     for origin in _TEST_ORIGINS:
+        origin_revisions = set()
         snp = snapshot_get_latest(storage, origin["url"])
+        swhids.append(
+            QualifiedSWHID(
+                object_type=ObjectType.SNAPSHOT, object_id=snp.id, origin=origin["url"]
+            )
+        )
         snapshots.add(hash_to_hex(snp.id))
         for branch_name, branch_data in snp.branches.items():
             target_type = branch_data.target_type.value
             if target_type == "revision":
-                revisions.add(branch_data.target)
+                origin_revisions.add(branch_data.target)
+                swhids.append(
+                    QualifiedSWHID(
+                        object_type=ObjectType.REVISION,
+                        object_id=branch_data.target,
+                        origin=origin["url"],
+                        visit=CoreSWHID(
+                            object_type=ObjectType.SNAPSHOT, object_id=snp.id
+                        ),
+                    )
+                )
                 if b"master" in branch_name:
                     # Add some origin intrinsic metadata for tests
                     metadata = common_metadata
@@ -310,14 +328,24 @@
                     )
             elif target_type == "release":
                 release = storage.release_get([branch_data.target])[0]
-                revisions.add(release.target)
+                origin_revisions.add(release.target)
                 releases.add(hash_to_hex(branch_data.target))
+                swhids.append(
+                    QualifiedSWHID(
+                        object_type=ObjectType.RELEASE,
+                        object_id=branch_data.target,
+                        origin=origin["url"],
+                        visit=CoreSWHID(
+                            object_type=ObjectType.SNAPSHOT, object_id=snp.id
+                        ),
+                    )
+                )
 
-        for rev_log in storage.revision_shortlog(set(revisions)):
+        for rev_log in storage.revision_shortlog(origin_revisions):
             rev_id = rev_log[0]
             revisions.add(rev_id)
 
-        for rev in storage.revision_get(revisions):
+        for rev in storage.revision_get(origin_revisions):
             if rev is None:
                 continue
             dir_id = rev.directory
@@ -328,8 +356,36 @@
                     content_path[entry["sha1"]] = "/".join(
                         [hash_to_hex(dir_id), entry["path"].decode("utf-8")]
                     )
+                    swhids.append(
+                        QualifiedSWHID(
+                            object_type=ObjectType.CONTENT,
+                            object_id=entry["sha1_git"],
+                            origin=origin["url"],
+                            visit=CoreSWHID(
+                                object_type=ObjectType.SNAPSHOT, object_id=snp.id
+                            ),
+                            anchor=CoreSWHID(
+                                object_type=ObjectType.REVISION, object_id=rev.id
+                            ),
+                            path=b"/" + entry["path"],
+                        )
+                    )
                 elif entry["type"] == "dir":
                     directories.add(hash_to_hex(entry["target"]))
+                    swhids.append(
+                        QualifiedSWHID(
+                            object_type=ObjectType.DIRECTORY,
+                            object_id=entry["target"],
+                            origin=origin["url"],
+                            visit=CoreSWHID(
+                                object_type=ObjectType.SNAPSHOT, object_id=snp.id
+                            ),
+                            anchor=CoreSWHID(
+                                object_type=ObjectType.REVISION, object_id=rev.id
+                            ),
+                            path=b"/" + entry["path"] + b"/",
+                        )
+                    )
 
     _add_extra_contents(storage, sha1s)
 
@@ -413,7 +469,7 @@
         "releases": list(releases),
         "revisions": list(map(hash_to_hex, revisions)),
         "snapshots": list(snapshots),
-        "generated_checksums": set(),
+        "swhids": swhids,
     }
 
 
diff --git a/swh/web/tests/misc/test_iframe.py b/swh/web/tests/misc/test_iframe.py
new file mode 100644
--- /dev/null
+++ b/swh/web/tests/misc/test_iframe.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2021  The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from hypothesis import given
+
+from swh.model.hashutil import hash_to_bytes
+from swh.model.identifiers import CoreSWHID, ObjectType
+from swh.web.common.utils import reverse
+from swh.web.tests.strategies import (
+    content_swhid,
+    directory_swhid,
+    revision_swhid,
+    unknown_directory,
+)
+from swh.web.tests.utils import check_html_get_response
+
+
+@given(content_swhid())
+def test_content_swhid_iframe(client, content_swhid):
+    url = reverse("swhid-iframe", url_args={"swhid": str(content_swhid)})
+    check_html_get_response(
+        client, url, status_code=200, template_used="misc/iframe.html"
+    )
+
+
+@given(content_swhid())
+def test_content_core_swhid_iframe(client, content_swhid):
+    content_core_swhid = CoreSWHID(
+        object_type=content_swhid.object_type, object_id=content_swhid.object_id
+    )
+    url = reverse("swhid-iframe", url_args={"swhid": str(content_core_swhid)})
+    check_html_get_response(
+        client, url, status_code=200, template_used="misc/iframe.html"
+    )
+
+
+@given(directory_swhid())
+def test_directory_swhid_iframe(client, directory_swhid):
+    url = reverse("swhid-iframe", url_args={"swhid": str(directory_swhid)})
+    check_html_get_response(
+        client, url, status_code=200, template_used="misc/iframe.html"
+    )
+
+
+@given(directory_swhid())
+def test_directory_core_swhid_iframe(client, directory_swhid):
+    directory_core_swhid = CoreSWHID(
+        object_type=directory_swhid.object_type, object_id=directory_swhid.object_id
+    )
+    url = reverse("swhid-iframe", url_args={"swhid": str(directory_core_swhid)})
+    check_html_get_response(
+        client, url, status_code=200, template_used="misc/iframe.html"
+    )
+
+
+@given(revision_swhid())
+def test_iframe_unsupported_object(client, revision_swhid):
+    url = reverse("swhid-iframe", url_args={"swhid": str(revision_swhid)})
+    check_html_get_response(
+        client, url, status_code=400, template_used="misc/iframe.html"
+    )
+
+
+@given(unknown_directory())
+def test_iframe_object_not_found(client, unknown_directory):
+    swhid = CoreSWHID(
+        object_type=ObjectType.DIRECTORY, object_id=hash_to_bytes(unknown_directory)
+    )
+    url = reverse("swhid-iframe", url_args={"swhid": str(swhid)})
+    check_html_get_response(
+        client, url, status_code=404, template_used="misc/iframe.html"
+    )
+
+
+@given(content_swhid())
+def test_swhid_iframe_unknown_error(client, mocker, content_swhid):
+    mocker.patch("swh.web.misc.iframe.get_swhid").side_effect = Exception("Error")
+    url = reverse("swhid-iframe", url_args={"swhid": str(content_swhid)})
+    check_html_get_response(
+        client, url, status_code=500, template_used="misc/iframe.html"
+    )
diff --git a/swh/web/tests/strategies.py b/swh/web/tests/strategies.py
--- a/swh/web/tests/strategies.py
+++ b/swh/web/tests/strategies.py
@@ -23,6 +23,7 @@
 from swh.model.hashutil import DEFAULT_ALGORITHMS, hash_to_bytes, hash_to_hex
 from swh.model.hypothesis_strategies import origins as new_origin_strategy
 from swh.model.hypothesis_strategies import snapshots as new_snapshot
+from swh.model.identifiers import ObjectType
 from swh.model.model import (
     Content,
     Directory,
@@ -646,3 +647,51 @@
             "rev_dir_rev_path": "libtess2",
         }
     )
+
+
+def swhid():
+    """
+    Hypothesis strategy returning a qualified SWHID for any object
+    ingested into the test archive.
+    """
+    return _known_swh_object("swhids")
+
+
+def content_swhid():
+    """
+    Hypothesis strategy returning a qualified SWHID for a content object
+    ingested into the test archive.
+    """
+    return swhid().filter(lambda swhid: swhid.object_type == ObjectType.CONTENT)
+
+
+def directory_swhid():
+    """
+    Hypothesis strategy returning a qualified SWHID for a directory object
+    ingested into the test archive.
+    """
+    return swhid().filter(lambda swhid: swhid.object_type == ObjectType.DIRECTORY)
+
+
+def release_swhid():
+    """
+    Hypothesis strategy returning a qualified SWHID for a release object
+    ingested into the test archive.
+    """
+    return swhid().filter(lambda swhid: swhid.object_type == ObjectType.RELEASE)
+
+
+def revision_swhid():
+    """
+    Hypothesis strategy returning a qualified SWHID for a revision object
+    ingested into the test archive.
+    """
+    return swhid().filter(lambda swhid: swhid.object_type == ObjectType.REVISION)
+
+
+def snapshot_swhid():
+    """
+    Hypothesis strategy returning a qualified SWHID for a snapshot object
+    ingested into the test archive.
+    """
+    return swhid().filter(lambda swhid: swhid.object_type == ObjectType.SNAPSHOT)